package com.share.service.impl;

import cn.hutool.core.util.RandomUtil;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.share.common.core.constant.MessageConstant;
import com.share.common.core.context.SecurityContextHolder;
import com.share.common.core.domain.R;
import com.share.common.core.exception.ServiceException;
import com.share.common.core.utils.StringUtils;
import com.share.domain.MongoDB.StationLocation;
import com.share.domain.MySql.Cabinet;
import com.share.domain.MySql.CabinetSlot;
import com.share.domain.MySql.PowerBank;
import com.share.domain.MySql.Station;
import com.share.domain.Vo.AvailableProwerBankVo;
import com.share.domain.Vo.StationVo;
import com.share.emqx.EmqxClientWrapper;
import com.share.emqx.constant.EmqxConstants;
import com.share.mapper.CabinetSlotMapper;
import com.share.mapper.PowerBankMapper;
import com.share.order.api.RemoteOrderInfoService;
import com.share.order.domain.OrderInfo;
import com.share.order.domain.ScanChargeVo;
import com.share.rabbit.constant.MqConst;
import com.share.rabbit.service.RabbitService;
import com.share.rule.api.RemoteFeeRuleService;
import com.share.rule.domain.FeeRule;
import com.share.service.*;
import com.share.user.api.RemoteUserService;
import com.share.user.domain.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DeviceServiceImpl implements IDeviceService {


    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private StationService stationService;

    @Autowired
    private ICabinetService cabinetService;

    @Autowired
    private IMapService mapService;

    @Autowired
    private RemoteFeeRuleService remoteFeeRuleService;

    @Autowired
    private EmqxClientWrapper emqxClientWrapper;

    @Autowired
    private CabinetSlotMapper cabinetSlotMapper;

    @Autowired
    private PowerBankMapper powerBankMapper;

    @Autowired
    private RabbitService rabbitService;
    @Autowired
    private CabinetSlotService cabinetSlotService;

    /**
     * 获取附近站点信息列表
     *
     * @param latitude
     * @param longitude
     * @return
     */
    @Override
    public List<StationVo> nearbyStation(String latitude, String longitude) {
        // 确定中心点
        GeoJsonPoint geoJsonPoint = new GeoJsonPoint(Double.parseDouble(longitude), Double.parseDouble(latitude));

        // 设置查询半径，比如查询50公里
        Distance distance = new Distance(50, Metrics.KILOMETERS);

        // 画圆
        Circle circle = new Circle(geoJsonPoint, distance);

        // 查询MongoDB数据
        Query locationQuery = Query.query(Criteria.where("location").withinSphere(circle));
        List<StationLocation> list = mongoTemplate.find(locationQuery, StationLocation.class);

        // 如果MongoDB查询结果为空，直接返回空列表
        if (list.isEmpty()) {
            return Collections.emptyList();
        }

        // 提取所有stationId
        List<Long> collect = list.stream().map(StationLocation::getStationId).collect(Collectors.toList());

        // 查询MySQL数据
        LambdaQueryWrapper<Station> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Station::getDelFlag, "0"); // 添加通用条件
        if (!collect.isEmpty()) {
            wrapper.in(Station::getId, collect);
        }
        List<Station> stationList = stationService.list(wrapper);

        // 封装结果
        List<StationVo> stationVoList = new ArrayList<>();
        //遍历
        stationList.forEach(station -> {
            // Station --  StationVo
            StationVo stationVo = new StationVo();
            BeanUtils.copyProperties(station, stationVo);

            //计算距离
            Double distanceStation = mapService.calculateDistance(longitude, latitude,
                    station.getLongitude().toString(),
                    station.getLatitude().toString());
            stationVo.setDistance(distanceStation);

            // 查询柜机信息
            Cabinet cabinet = cabinetService.getOne(
                    new LambdaQueryWrapper<Cabinet>()
                            .eq(Cabinet::getId, station.getCabinetId())
                            .select(Cabinet::getAvailableNum, Cabinet::getFreeSlots)
            );

            // 设置可借/可还状态
            stationVo.setIsUsable(cabinet != null && cabinet.getAvailableNum() > 0 ? "1" : "0");
            stationVo.setIsReturn(cabinet != null && cabinet.getFreeSlots() > 0 ? "1" : "0");

            //获取站点规则数据
            Long feeRuleId = station.getFeeRuleId();
            R<FeeRule> feeRuleResult = remoteFeeRuleService.getFeeRule(feeRuleId);
            FeeRule feeRule = feeRuleResult.getData();
            if (feeRule != null) {
                String description = feeRule.getDescription();
                stationVo.setFeeRule(description);
            }

            stationVoList.add(stationVo);
        });

        return stationVoList;
    }


    /**
     * 根据id获取门店详情
     *
     * @param id
     * @param latitude
     * @param longitude
     * @return
     */
    @Override
    public StationVo getStation(Long id, String latitude, String longitude) {
        //根据门店id获取详情
        Station station = stationService.getById(id);
        //封装到StationVo
        StationVo stationVo = new StationVo();
        BeanUtils.copyProperties(station, stationVo);
        //当前位置距离门店距离
        Double distance = mapService.calculateDistance(longitude, latitude,
                station.getLongitude().toString(),
                station.getLatitude().toString());
        //将距离封装到StationVo
        stationVo.setDistance(distance);

        // 获取柜机信息
        Cabinet cabinet = cabinetService.getById(station.getCabinetId());
        //可用充电宝数量大于0，可借用
        if (cabinet.getAvailableNum() > 0) {
            stationVo.setIsUsable("1");
        } else {
            stationVo.setIsUsable("0");
        }
        // 获取空闲插槽数量大于0，可归还
        if (cabinet.getFreeSlots() > 0) {
            stationVo.setIsReturn("1");
        } else {
            stationVo.setIsReturn("0");
        }

        // 获取费用规则
        Long feeRuleId = station.getFeeRuleId();
        FeeRule feeRule = remoteFeeRuleService.getFeeRule(feeRuleId).getData();
        if (feeRule != null) {
            // 将费用规则描述封装到StationVo
            stationVo.setFeeRule(feeRule.getDescription());
        }
        return stationVo;
    }


    @Autowired
    private RemoteUserService remoteUserService;

    @Autowired
    private RemoteOrderInfoService remoteOrderInfoService;



    /**
     * 扫码充电接口
     *
     * @param cabinetNo 柜机编号
     * @return 充电信息
     */
    @Override
    public ScanChargeVo scanCharge(String cabinetNo) {
        // 扫码充电返回对象
        ScanChargeVo scanChargeVo = new ScanChargeVo();

        try {
            // 1 远程调用：根据当前登录用户id查询用户信息，
            // 从用户信息获取是否支持免押金充电
            R<UserInfo> userInfoR = remoteUserService.getInfo(SecurityContextHolder.getUserId());
            UserInfo userInfo = userInfoR.getData();
            // 判断
            if (userInfo == null) {
                throw new ServiceException(MessageConstant.FAIL_GET_USER_INFO);
            }
            // 判断是否支持免押金
            if ("0".equals(userInfo.getDepositStatus())) {
                throw new ServiceException(MessageConstant.NOT_APY_FREE_DEPOSIT);
            }
            // 可以做让用户交押金业务代码开发.....

            // 2 远程调用：判断用户是否有未完成订单
            R<OrderInfo> orderInfoR = remoteOrderInfoService.getNoFinishOrder(SecurityContextHolder.getUserId());
            OrderInfo orderInfo = orderInfoR.getData();
            // 判断是否有未完成订单
            if (orderInfo != null) {
                // 有未完成订单
                String status = orderInfo.getStatus();
                if ("0".equals(status)) {
                    scanChargeVo.setStatus("2");
                    scanChargeVo.setMessage(MessageConstant.THERE_IS_NO_RETURN_POWER_BANK);
                    return scanChargeVo;
                }
                if ("1".equals(status)) {
                    scanChargeVo.setStatus("3");
                    scanChargeVo.setMessage(MessageConstant.THERE_ARE_UNPAID_ORDERS);
                    return scanChargeVo;
                }
            }

            // 3 从柜机里面获取最优充电宝
            AvailableProwerBankVo availableProwerBankVo = this.checkAvailableProwerBank(cabinetNo);
            // 判断
            if (availableProwerBankVo == null) {
                throw new ServiceException(MessageConstant.NO_AVAILABLE_POWER_BANK);
            }
            // 如果错误提示信息不为空，则抛出ServiceException异常
            if (!StringUtils.isEmpty(availableProwerBankVo.getErrMessage())) {
                throw new ServiceException(availableProwerBankVo.getErrMessage());
            }

            // 4 把选择最优充电宝弹出
            // 使用MQTT弹出充电宝
            // (1) 创建一个JSONObject实例用于存储消息信息
            JSONObject object = new JSONObject();
            // (2) 将当前用户ID存入JSONObject中，键名为"uId"
            object.put("uId", SecurityContextHolder.getUserId());
            // (3) 生成一个格式为"mm"后跟8位随机字符的字符串，并存入JSONObject中，键名为"mNo"
            object.put("mNo", "mm" + RandomUtil.randomString(8));
            // (4) 将柜子编号存入JSONObject中，键名为"cNo"
            object.put("cNo", cabinetNo);
            // (5) 将可用电源银行编号存入JSONObject中，键名为"pNo"
            object.put("pNo", availableProwerBankVo.getPowerBankNo());
            // (6) 将插槽编号存入JSONObject中，键名为"sNo"
            object.put("sNo", availableProwerBankVo.getSlotNo());
            // (7) 使用EmqxConstants中的常量和柜子编号格式化主题名称
            String topic = String.format(EmqxConstants.TOPIC_SCAN_SUBMIT, cabinetNo);
            // (8) 调用emqxClientWrapper的publish方法，发布主题和JSONObject转换成的字符串消息
            emqxClientWrapper.publish(topic, object.toJSONString());

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            // 5 返回封装需要数据
            scanChargeVo.setStatus("1");
            return scanChargeVo;
        } catch (Exception e) {
            // 处理异常
            scanChargeVo.setStatus("0");
            scanChargeVo.setMessage(e.getMessage());
            return scanChargeVo;
        }
    }


    /**
     * 根据柜机编号获取一个可用最优的充电宝
     *
     * @param cabinetNo 柜机编号
     * @return
     */
    private AvailableProwerBankVo checkAvailableProwerBank(String cabinetNo) {
        //1 创建AvailablePowerBankVo对象
        AvailableProwerBankVo availableProwerBankVo = new AvailableProwerBankVo();

        //2 根据cabinetNo柜机编号查询柜机信息
        Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);

        //3 判断柜机中可用充电宝数量是否大于0
        Integer availableNum = cabinet.getAvailableNum();
        if (availableNum == 0) {
            availableProwerBankVo.setErrMessage(MessageConstant.NO_AVAILABLE_POWER_BANK);
            return availableProwerBankVo;
        }

        //4 根据柜机id查询插槽列表，返回list集合
        List<CabinetSlot> cabinetSlotList = cabinetSlotMapper
                .selectList(new LambdaQueryWrapper<CabinetSlot>()
                        .eq(CabinetSlot::getCabinetId, cabinet.getId()));


        //5 从第四步返回插槽列表list集合 获取对应充电宝id集合
        List<Long> powerBankIdList = cabinetSlotList.stream()
                .filter(item -> null != item.getPowerBankId())
                .map(CabinetSlot::getPowerBankId).collect(Collectors.toList());

        //6 根据充电宝id列表查询对应充电宝信息
        LambdaQueryWrapper<PowerBank> wrapperPowerBank = new LambdaQueryWrapper<>();
        wrapperPowerBank.in(PowerBank::getId, powerBankIdList);
        wrapperPowerBank.eq(PowerBank::getStatus, "1");
        List<PowerBank> powerBankList = powerBankMapper.selectList(wrapperPowerBank);
        //判断集合是否为空
        if (CollectionUtils.isEmpty(powerBankList)) {
            availableProwerBankVo.setErrMessage(MessageConstant.NO_AVAILABLE_POWER_BANK);
            return availableProwerBankVo;
        }

        //7 把上一步获取充电宝信息集合进行排序（根据电量排序）
        // 如果集合大于1，则进行排序
        if (powerBankList.size() > 1) {
            //根据电量值对充电宝列表进行降序排序
            Collections.sort(powerBankList,
                    ((o1, o2) -> o2.getElectricity().compareTo(o1.getElectricity())));
        }

        //8 获取电量最多的充电宝信息
        PowerBank powerBank = powerBankList.get(0);

        //9 获取电量最多的充电宝对应插槽信息
        CabinetSlot cabinetSlot = cabinetSlotList.stream()
                .filter(item -> null != item.getPowerBankId()
                        && item.getPowerBankId().equals(powerBank.getId()))
                .collect(Collectors.toList()).get(0);

        //10 锁定插槽（更新插槽状态）
        cabinetSlot.setStatus("2");
        cabinetSlotMapper.updateById(cabinetSlot);

        //11 返回需要vo数据
        availableProwerBankVo.setSlotNo(cabinetSlot.getSlotNo());
        availableProwerBankVo.setPowerBankNo(powerBank.getPowerBankNo());

        // 扫码后未弹出充电宝等情况，延迟解锁
        //rabbitService.sendDealyMessage(MqConst.EXCHANGE_DEVICE, MqConst.ROUTING_UNLOCK_SLOT, JSONObject.toJSONString(cabinetSlot), MqConst.CANCEL_UNLOCK_SLOT_DELAY_TIME);

        return availableProwerBankVo;
    }


    @Override
    public void unlockSlot(CabinetSlot cs) {
        CabinetSlot cabinetSlot = cabinetSlotService.getById(cs.getId());
        if ("2".equals(cabinetSlot.getStatus())) {
            //状态（1：占用 0：空闲 2：锁定）
            cabinetSlot.setStatus("1");
            cabinetSlot.setUpdateTime(new Date());
            cabinetSlotService.updateById(cabinetSlot);
        }
    }

}